/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package jigdo.DiskIO;

import java.io.IOException;
import java.util.logging.*;
import java.nio.file.Files;
import java.nio.file.LinkOption;
import java.nio.file.Paths;
import java.security.NoSuchAlgorithmException;
import jigdo.JigdoException;
import jigdo.data.template.section.DESCEntries.*;
import jigdo.downloader.DownloadEntry;
import jigdo.downloader.DownloadStatus;
import jigdo.utils.FileUtils;
import jigdo.utils.MD5Helpers;

/**
 * Class utilized by the download system to write files to the ISO.
 *
 * @author John R Sohn
 * @see jigdo.downloader.ImageDownloadTask
 * @see jigdo.downloader.DownloadEntry
 */
public class ISOWriter extends Thread {

    public void setParentLogger(Logger Parent) {
        logger.setParent(Parent);
        logger.setLevel(null);
    }
    Logger logger = Logger.getLogger(this.getClass().getName());
    private Object locker = new Object();

    public void Flush() {
        synchronized (locker) {
            locker.notifyAll();
        }
    }
    boolean persist = true;

    @Override
    public void run() {

        while (persist) {
            try {
                this.wait();
            } catch (Exception e) {
            }

            java.util.ArrayList<DownloadEntry> entries = new java.util.ArrayList<DownloadEntry>();

            synchronized (waiting) {
                while (!waiting.empty()) {
                    entries.add(waiting.pop());
                }

            }

            try {
                java.io.RandomAccessFile fs = new java.io.RandomAccessFile(Parent.jigdofile.Filename, "rw");

                for (int x = 0; x < entries.size(); x++) {
                    DownloadEntry curr = entries.get(x);

                    fs.seek(curr.TemplateEntry.FileOffset);
                    fs.write(curr.DownloadedBytes);
                    // curr.TemplateEntry.FileOffset
                    curr.Progress.Written = true;
                    curr.DownloadedBytes = null;


                }

                fs.close();
                this.Progress.WriteThis();

            } catch (Exception e) {
                Parent.setError("Error while writing to image", e);

            }
        }

    }
    public ProgressFile Progress;
    /**
     * The owning ImageDownloadTask
     */
    public jigdo.downloader.ImageDownloadTask Parent;
    /**
     * The filepath of the ISO.
     */
    public String Filename;
    /**
     * Contains a queue of finished downloads waiting to be committed to the
     * image.
     */
    public java.util.Stack<jigdo.downloader.DownloadEntry> waiting;

    /**
     * Constructor specifying download parameters for the jigdo job.
     *
     * @param fileName The target .iso filename
     * @param parent The parent download task.
     */
    public ISOWriter(String fileName, jigdo.downloader.ImageDownloadTask parent) throws JigdoException {
        Parent = parent;
        Filename = fileName;
        waiting = new java.util.Stack<jigdo.downloader.DownloadEntry>();
        Progress = new ProgressFile(fileName.trim() + ".progress", true, this);


    }

    /**
     * Writes blank ISO to the disk at the location specified
     */
    public void WriteStartingISO(boolean clearData)
            throws
            java.io.FileNotFoundException,
            java.io.IOException,
            JigdoException{
        logger.log(Level.INFO, "Writing Initial File: " + Filename);

        if (!Files.exists(Paths.get(Filename), LinkOption.NOFOLLOW_LINKS)) {
            Progress.InitialImageWritten = false;
            Progress.WriteThis();

            logger.log(Level.INFO, "No Image Found, writing initial");

            long currOffset = 0;

            java.io.FileOutputStream fs = new java.io.FileOutputStream(Filename);

            logger.finest("Entering write loop of initial iso");

            for (int x = 0; x < Parent.jigdotemplate.Description.Entries.size(); x++) {

                logger.finest("At Description Entry: " + Integer.toString(x));

                jigdo.data.template.section.DESCEntries.TemplateDESCEntry currEntry = Parent.jigdotemplate.Description.Entries.get(x);


                if (currEntry.IsRawData()) {
                    logger.finest("Current entry is raw data");

                    TemplateDESCRawDataEntry raw = (TemplateDESCRawDataEntry) currEntry;

                    logger.finest("Iterating through matched data sources...");

                    for (int srcIndex = 0; srcIndex < raw.SourceRanges.size(); srcIndex++) {
                        logger.finest("At index: " + Integer.toString(x));

                        DATARange dr = raw.SourceRanges.get(srcIndex);

                        logger.finest(dr.toString());
                        logger.finest(dr.Source.toString());

                        fs.write(dr.Source.UncompressedData, dr.StartIndex, dr.getLength());
                    }
                } else if (currEntry.IsFileEntry()) {
                    logger.finest("Is File entry");

                    TemplateDESCFileInfoEntry filed = (TemplateDESCFileInfoEntry) currEntry;

                    byte[] buff = new byte[(int) filed.FileLength];
                    java.util.Arrays.fill(buff, (byte) 0);

                    fs.write(buff, 0, buff.length);

                    buff = null;

                }

                fs.flush();
            }

            fs.close();

            Progress.InitialImageWritten = true;
            Progress.WriteThis();
            
            Parent.jigdotemplate.FreeData();

        } else {
            try
            {
                this.CheckExisting();
            }
            catch (Exception e)
            {
                throw new JigdoException("Error while checking existing image",e);
            }
        }

        
    }

    /**
     * Verifies the final iso.
     */
    public boolean VerifyImageMD5() throws NoSuchAlgorithmException,IOException {
        
        return MD5Helpers.GetJigdoMD5FromFile(Filename).
                equals(Parent.jigdotemplate.Description.ImageInfo.ImageMD5);
        
    }

    private boolean CheckSize() throws IOException {
        return Files.size(Paths.get(this.Filename)) == 
                Parent.jigdotemplate.Description.ImageInfo.ImageLength;
    }

    /**
     * Checks an existing ISO image and file structure for resume.
     */
    public void CheckExisting() throws IOException,JigdoException {

        if (!CheckSize()) {
            Progress.InitialImageWritten = false;
            Progress.ResetAll();
            FileUtils.DeleteIfExists(Filename);
            this.WriteStartingISO(true);
            return;
        }

        for (int x = 0; x < Progress.Entries.size(); x++) {
            ProgressEntry curr = Progress.Entries.get(x);

            try {
                if (!MD5Helpers.GetJigdoMD5FromFilePart(Filename,
                        curr.Parent.TemplateEntry.FileOffset, curr.Parent.TemplateEntry.FileLength)
                        .equals(curr.FileMD5)) {
                    
                    curr.Written=false;
                    curr.Parent.setDownloadStatus(DownloadStatus.NotStarted);
                }
                else
                {
                    curr.Written=true;
                    curr.Parent.setDownloadStatus(DownloadStatus.Finished);
                }
               

            } catch (Exception e) {
                
                throw new JigdoException("Error occurred while verifying downloaded file in iso",e);
            }

        }
        
        Progress.WriteThis();
    }

    /**
     * Adds an entry to the commit queue
     *
     * @param entry
     */
    public void QueueWrite(jigdo.downloader.DownloadEntry entry) {
        synchronized (waiting) {
            waiting.push(entry);
        }
        
        Flush();
    }
}
